/**
 * Project: DownOpds: Téléchargement de catalogue OPDS
 * File name: ParseOpds.java
 * Description:   Classe Utilitaire qui va parser le catalogue OPDS
 *
 * @author Fabien Silberstein, Silberfab, Copyright (C) 2011.
 *
 * @see The GNU Public License (GPL)
 */

package helper;

import downloadopds.model.CatalogueOpds;
import downloadopds.model.Message;
import java.awt.Color;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import javax.swing.SwingWorker;
import main.DownloadOPDSView;
import main.model.Ebook;

/**
 * Classe Utilitaire qui va parser le catalogue OPDS
 * @author Silberfab
 */
public class ParseOpds extends SwingWorker<Integer, Message> {

    /*Déclaration des variables globales*/
    private Document doc = null;
    private CatalogueOpds catalogue;
    private StyledDocument docStyle;
    private DownloadOPDSView view;

    //Variables pour l'affichage
    private int cptPages = 0;
    private int cptDl = 0;

    //Déclaration des styles
    Style erreurStyle;
    Style infoStyle;
    Style okStyle;

    public ParseOpds(DownloadOPDSView _view, CatalogueOpds _catalog) {
        this.view = _view;
        this.catalogue = _catalog;

        //On applique les règles sur les boutons
        this.view.stopButton.setEnabled(true);
        this.view.downloadButton.setEnabled(false);

        docStyle = (StyledDocument) this.view.logArea.getDocument();

        //Création des styles
        erreurStyle = docStyle.addStyle("Erreur", null);
        infoStyle = docStyle.addStyle("Info", null);
        okStyle = docStyle.addStyle("OK", null);

        //Configuration des styles
        StyleConstants.setForeground(erreurStyle, Color.RED);
        StyleConstants.setForeground(infoStyle, Color.BLACK);
        StyleConstants.setForeground(okStyle, Color.GREEN);
    }

    public Integer doInBackground() {
        //On débute le téléchargement
        this.InitDoc(this.catalogue.getIndex() + this.catalogue.getPageCible());
        return this.StartParse();
    }

    @Override
    protected void process(List<Message> messages) {
        /* Affichage des publications reçues dans le textarea. */
        for(Message m : messages){
            try {
                docStyle.insertString(docStyle.getLength(), m.GetTexte()+"\n", m.GetStyle());
            } catch (BadLocationException ex) {
                Logger.getLogger(DownloadOPDSView.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        this.view.logArea.setCaretPosition(this.view.logArea.getDocument().getLength());
    }

    @Override
    protected void done() {
       try {
           this.view.statusMessageLabel.setText("");
           this.view.progressBar.setString("");
           this.view.progressBar.setValue(0);
           this.view.progressBar.setIndeterminate(false);

           if(this.isCancelled()){
                publish(new Message("Traitement interrompu par l'utilisateur : "+cptDl+" livre(s) téléchargé(s)",infoStyle));
            }
            else {
                publish(new Message("Traitement terminé, "+cptDl+" livre(s) téléchargé(s)", infoStyle));
            }

           this.view.stopButton.setEnabled(false);
           this.view.downloadButton.setEnabled(true);
       }
       catch (Exception ignore) {
       }
    }

    /**
     * Initialisation du document html à parser
     * @param adresse L'adresse de la page à parser
     */
    private void InitDoc(String adresse) {
        cptPages++;
        try {
            //Connection au catalogue
            docStyle.insertString(docStyle.getLength(), "Connexion à la page "+cptPages+".\n", infoStyle);
            doc = Jsoup.connect(adresse).get();
        }
        catch (IOException ex) {
            publish(new Message("Impossible de se connecter à l'adresse suivante : "+adresse, erreurStyle));
            Logger.getLogger(DownloadOPDSView.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (BadLocationException ex) {
            publish(new Message("Impossible de se connecter à l'adresse suivante : "+adresse, erreurStyle));
            Logger.getLogger(DownloadOPDSView.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Processus de téléchargement du fichier
     * @param host Adresse du fichier à télécharger
     * @param fileName Le nom du fichier
     */
    private void GetFile(String host, Ebook ebook) {
        InputStream input = null;
        FileOutputStream writeFile = null;

        try
        {
            URL url = new URL(host);
            URLConnection connection = url.openConnection();
            int fileLength = connection.getContentLength();

            if (fileLength == -1)
            {
                publish(new Message("Url invalide pour le téléchargement : "+host,erreurStyle));
                publish(new Message("DEBUG connection : "+connection.toString(),erreurStyle));
                return;
            }

            //Avant d'écrire un fichier, on vérifie son existence préalable
            File f = new File(this.catalogue.getDirDest()+"/"+ebook.getNomFichier());
            if ( f.exists() ) {
                //Le livre existe déjà, on test la taille du fichier.
                //Si différente on télécharge le fichier.
                if(f.length() == fileLength) {
                    return;
                }
            }

            input = connection.getInputStream();

            //Création du fichier
            writeFile = new FileOutputStream(this.catalogue.getDirDest()+"/"+ebook.getNomFichier());
            byte[] buffer = new byte[1024];
            int read;

            while ((read = input.read(buffer)) > 0)
                writeFile.write(buffer, 0, read);
            writeFile.flush();

            cptDl++;
            publish(new Message("Téléchargement de "+ebook.getNomFichier()+" OK.",okStyle));
            ebook.setEchec(false);
        }
        catch (IOException e)
        {
            ebook.setEchec(true);
            publish(new Message("Une erreur s'est produite durant le téléchargement du fichier : " + ebook.getNomFichier(),erreurStyle));
            Logger.getLogger(DownloadOPDSView.class.getName()).log(Level.WARNING, null, e);
        }
        finally
        {
            try
            {
                if(writeFile != null) {
                    writeFile.close();
                    input.close();
                }
            }
            catch (IOException e)
            {
                Logger.getLogger(DownloadOPDSView.class.getName()).log(Level.WARNING, null, e);
            }
        }
    }

    /**
     * Initialise la procedure de parsing pour une page
     * @return
     */
    private Integer StartParse() {
        if (doc != null && !this.isCancelled()) {
            Element conteneur = doc.body().getElementsByAttributeValue("class", "browseByCover").first();

            if(conteneur == null){
                conteneur = doc.body().getElementsByAttributeValue("class", "browseByList").first();
            }

            //Mise à jour de la barre de progression
            SetProgress();

            //On lance le parsing de l'élément
            ParsePage(conteneur);

            //Si il y a une page suivante, on la parse
            Elements pageSuivante = doc.body().getElementsByAttributeValue("class", "buttonwrapper");
            if(!pageSuivante.isEmpty() && !this.isCancelled())
            {
                String adresse = pageSuivante.first().select("a").attr("href").replaceFirst("../", "");
                InitDoc(this.catalogue.getIndex() + adresse);
                StartParse();
            }
        }
        return 0;
    }

    /**
     * Procédure de mise à jour de la progression (affichage)
     */
    private void SetProgress(){
        if(cptPages == 1){
            //Premier passage, on parse la page pour connaitre le nombre de pages total
            try{
                String buttonPage = doc.body().getElementsByAttributeValue("class", "buttonwrapper").first().select("span").text();
                int start = buttonPage.indexOf("sur ")+4;
                int length = buttonPage.indexOf(")");
                this.catalogue.setNbPages(Integer.parseInt(buttonPage.substring(start,length)));
            }
            catch(Exception e){
                this.catalogue.setNbPages(1);
            }
            finally{
                if(this.catalogue.getNbPages() > 1){
                    this.view.progressBar.setIndeterminate(false);
                    this.view.progressBar.setMaximum(this.catalogue.getNbPages());
                }
                //Sinon quand il n'y a qu'une page, on laisse en indeterminate
            }
        }

        //Affichage de la progression
        this.view.progressBar.setValue(cptPages);
        this.view.progressBar.setString("Page " + cptPages + "/" + this.catalogue.getNbPages());
    }

    /**
     * Procédure qui parse chaque livre et le télécharge
     * @param conteneur L'élément (div) qui contient les livres
     */
    private void ParsePage(Element conteneur) {
        //On parcours ensuite tous les livres dans le conteneur
        for (Element livre : conteneur.children()) {

            if(!this.isCancelled()){
                //Création de l'ebook
                Ebook ebook = new Ebook();

                //On récupère l'adresse de la cover
                Element e = livre.select("img").first();
                String cover = e.attr("src");
                ebook.setCoverUrl(cover);
                ebook.setTitre(e.attr("title"));

                //D'abord on enleve le caractere relatif de l'adresse
                cover = cover.replaceFirst("../", "");

                //Ensuite on prend le format choisi par l'utilisateur et non la cover
                String epub;
                String format = this.view.cbFormat.getSelectedItem().toString();
                if(format.equals("Epub")){
                    epub = cover.replaceFirst(".jpg", ".epub");
                }
                else if(format.equals("Mobi"))
                {
                    epub = cover.replaceFirst(".jpg", ".mobi");
                }
                else{
                    epub = cover.replaceFirst(".jpg", ".pdf");
                }
                ebook.setDownloadUrl(epub);

                this.view.statusMessageLabel.setText("Téléchargement...\t" + ebook.getTitre());

                //Téléchargement de l'epub
                GetFile(this.catalogue.getIndex()+epub, ebook);
            }
            else{
                break;
            }
        }
    }

}
